/*
 * Copyright [2022] [https://www.xiaonuo.vip]
 *
 * Snowy采用APACHE LICENSE 2.0开源协议，您在使用过程中，需要注意以下几点：
 *
 * 1.请不要删除和修改根目录下的LICENSE文件。
 * 2.请不要删除和修改Snowy源码头部的版权声明。
 * 3.本项目代码可免费商业使用，商业使用请保留源码和相关描述文件的项目出处，作者声明等。
 * 4.分发源码时候，请注明软件出处 https://www.xiaonuo.vip
 * 5.不可二次分发开源参与同类竞品，如有想法可联系团队xiaonuobase@qq.com商议合作。
 * 6.若您的项目无法满足以上几点，需要更多功能代码，获取Snowy商业授权许可，请在官网购买授权，地址为 https://www.xiaonuo.vip
 */
package vip.xiaonuo.dev.core.listener;

import cn.hutool.cron.CronUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import vip.xiaonuo.common.exception.CommonException;
import vip.xiaonuo.common.lock.DistributedLockExecutor;
import vip.xiaonuo.common.pojo.CommonResult;
import vip.xiaonuo.common.timer.CommonTimerTaskRunner;
import vip.xiaonuo.dev.modular.job.entity.DevJob;
import vip.xiaonuo.dev.modular.job.enums.DevJobStatusEnum;
import vip.xiaonuo.dev.modular.job.service.DevJobService;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.UUID;

/**
 * 定时任务监听器，系统启动时将定时任务启动
 *
 * @author xuyuxiang
 * @date 2022/8/5 16:07
 **/
@Slf4j
@Configuration
public class DevJobListener implements ApplicationListener<ApplicationStartedEvent>, Ordered {
    private final DistributedLockExecutor lockExecutor;

    // 通过构造函数注入
    public DevJobListener(DistributedLockExecutor lockExecutor) {
        this.lockExecutor = lockExecutor;
    }

    @Override
    public void onApplicationEvent(@NonNull ApplicationStartedEvent applicationStartedEvent) {
        //刘振洲修改，判断配置文件，以只启动一次定时任务，防止集群部署时重复执行定时任务
        SpringUtil.getBean(DevJobService.class).list(new LambdaQueryWrapper<DevJob>()
                .eq(DevJob::getJobStatus, DevJobStatusEnum.RUNNING.getValue()).orderByAsc(DevJob::getSortCode))
                .forEach(devJob -> {
                    // 为每个任务创建唯一的调度器
                    CronUtil.schedule(devJob.getId(), devJob.getCronExpression(), () -> {
                        executeJobWithLock(devJob);
                    });
                });
        // 设置秒级别的启用
        CronUtil.setMatchSecond(true);
        // 启动定时器执行器
        CronUtil.start();
    }
    /**
     * 使用分布式锁执行任务
     */
    private void executeJobWithLock(DevJob devJob) {
        // 构建锁的键，确保全局唯一
        String lockKey = "job:lock:" + devJob.getId();

        try {
            lockExecutor.executeWithLock(lockKey,
                    2000,   // 等待锁的最长时间 2秒
                    30000,  // 锁持有时间 30秒（应大于任务最大执行时间）
                    () -> {
                        try {
                            log.info("【集群】节点[{}] 开始执行任务: {}", getNodeId(), devJob.getName());

                            // 运行定时任务
                            Class<?> taskClass = Class.forName(devJob.getActionClass());
                            CommonTimerTaskRunner runner = (CommonTimerTaskRunner) SpringUtil.getBean(taskClass);
                            CommonResult result = runner.action(devJob.getExtJson());

                            log.info("【集群】节点[{}] 完成执行任务: {}，执行结果{}", getNodeId(), devJob.getName(),result);
                        } catch (ClassNotFoundException e) {
                            log.error("任务类加载失败: {}", devJob.getActionClass(), e);
                            throw new CommonException("定时任务找不到对应的类: {}", devJob.getActionClass());
                        } catch (Exception e) {
                            log.error("任务执行异常: {}", devJob.getName(), e);
                            throw e;
                        }
                    });
        } catch (DistributedLockExecutor.LockAcquisitionException e) {
            log.debug("【集群】节点[{}] 未获取到锁，跳过执行任务: {}", getNodeId(), devJob.getName());
        } catch (Exception e) {
            log.error("【集群】任务执行过程中发生错误: {}", devJob.getName(), e);
        }
    }

    /**
     * 获取当前节点ID（简化示例，实际使用需要唯一标识）
     */
    private String getNodeId() {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            return UUID.randomUUID().toString().substring(0, 8);
        }
    }
    @Override
    public int getOrder() {
        return LOWEST_PRECEDENCE;
    }
}
